package mint.modules.generator;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import mint.MintLexer;
import mint.modules.symbol.ClassSymbol;
import mint.modules.symbol.ConstructorSymbol;
import mint.modules.symbol.SymbolFactory;
import mint.modules.type.Type;

import org.antlr.runtime.tree.Tree;

public class ConstructorDeclarationNode extends Scope implements MintNode {
	private String name;
	private String modifier;	
	private boolean superCalled;
	private List<String> errorList;
	private List<FormalParameterNode> parameters;
	private int line;
	private boolean defaultCtor;
	private int localsSize;
	private int stackSize;
	private ConstructorBodyNode block;
	private ConstructorSymbol superConstructor;
	
	public ConstructorDeclarationNode(String className, Scope theParent)
	{
		super(theParent, theParent.containerClass);
		CounterManager.getInstance().initializeLocal();
		name = className;		
		modifier = "public";
		errorList = new ArrayList<String>();
		parameters = new ArrayList<FormalParameterNode>();
		line = 0;		
		defaultCtor = true;		
		localsSize = CounterManager.getInstance().getLocalNumber();
		stackSize = CounterManager.getInstance().incrementParameter();
		block = null;
		superConstructor = null;
	}

	public ConstructorDeclarationNode(Scope theParent, Tree ctorNode) {
		super(theParent, theParent.containerClass);		
		
		CounterManager.getInstance().initializeLocal();
		errorList = new ArrayList<String>();
		processConstructor(ctorNode);
		defaultCtor = false;		
		localsSize = CounterManager.getInstance().getLocalNumber();
		stackSize = CounterManager.getInstance().incrementParameter();
		superConstructor = null;
	}
	
	public String getName()
	{
		return "<init>";
	}
	
	public String getModifier()
	{
		return modifier;
	}
	
	public int getParameterListSize()
	{
		return parameters.size();
	}
	
	public ConstructorSymbol getSuperConstructor()
	{
		return superConstructor;
	}
	
	public FormalParameterNode getParameter(int i)
	{
		if(i >= 0 && i < parameters.size())
			return parameters.get(i);
		else
			return null;
	}
	
	public boolean isDefaultConstructor()
	{
		return defaultCtor;
	}
	
	public boolean isSuperCalled()
	{
		return superCalled;
	}
	
	public void setSuperCalled(boolean status)
	{
		superCalled = status;
	}

	@Override
	public void testSemantic() {
		if (name.equals(getContainerClass()))
		{
			ClassSymbol classSymbol = (ClassSymbol)getParent().searchSymbol(name, getContainerClass(), false);
			String superClass = classSymbol.getSuperClass();
			
			if (superClass != null)
			{
				ClassSymbol parentSymbol = (ClassSymbol)getParent().searchSymbol(superClass, getContainerClass(), false);
				
				
				superConstructor = (ConstructorSymbol)parentSymbol.getClassNode().searchSymbol("<init>", superClass, true);
			}
			
			for(FormalParameterNode param : parameters)
				errorList.addAll(param.getSemanticErrors());
			if (block != null)
				block.testSemantic();
		}
		else
			errorList.add(line + ": " + name + " isn't a valid Constructor name, expected " + getContainerClass());
	}

	@Override
	public List<String> getSemanticErrors() {
		for(FormalParameterNode param : parameters)
			errorList.addAll(param.getSemanticErrors());
		if (block != null)
			errorList.addAll(block.getSemanticErrors());
		return errorList;
	}

	@Override
	public void emit(Formatter output) {
		ClassSymbol classSymbol = (ClassSymbol)getParent().searchSymbol(name, getContainerClass(), false);		
		
		output.format(";Constructor %s\n", name);
		output.format(".method %s <init>(", modifier);
		for (FormalParameterNode param : parameters)
		{
			param.emit(output);
		}		
		output.format(")V\n");
		
		output.format(".limit stack %d\n", localsSize * 2 + stackSize);
		output.format(".limit locals %d\n", localsSize);
		classSymbol.getClassNode().emitInitializers(output);
		
		if (!defaultCtor)
		{
			if (!superCalled)
			{
				output.format("aload_0\n");
				
				if(classSymbol.getClassNode().getSuperName() == null)
					output.format("invokenonvirtual java/lang/Object/<init>()V\n");
				else
				{
					for (int i = 0; i < superConstructor.getParameterListSize(); i++)
					{
						FormalParameterNode param = superConstructor.getParameter(i);
						
						if (param.getType().equals(Type.intType) || param.getType().equals(Type.booleanType))
							output.format("iconst_0\n");
						else if (param.getType().equals(Type.floatType))
							output.format("fconst_0\n");
						else
							output.format("aconst_null\n");
					}
					output.format("invokenonvirtual %s/<init>(", classSymbol.getClassNode().getSuperName());
					for (int i = 0; i < superConstructor.getParameterListSize(); i++)
						output.format("%s", superConstructor.getParameter(i).getType().emit());
					output.format(")V\n");
				}			
			}
			
			block.emit(output);
		}
		else
		{	
			output.format("aload_0\n");
			
			if(classSymbol.getClassNode().getSuperName() == null)
				output.format("invokenonvirtual java/lang/Object/<init>()V\n");
			else
				output.format("invokenonvirtual %s/<init>()V\n", classSymbol.getClassNode().getSuperName());
		}
		output.format("return\n");
		output.format(".end method\n");
		output.format(";End Constructor %s\n", name);
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public void printSymbolTable(Formatter output, int tabSize) {
		if (block != null)
			block.printSymbolTable(output, tabSize + 1);
	}
	
	private void processConstructor(Tree ctorTree) {
		Tree firstChild = ctorTree.getChild(0);
		
		if (firstChild.getType() == MintLexer.MODIFIER)
		{
			modifier = firstChild.getChild(0).getText();		
			name = ctorTree.getChild(1).getText();
			processFormalParameterList(ctorTree.getChild(2));
			block = new ConstructorBodyNode(this, ctorTree.getChild(3));
		}
		else
		{
			modifier = "public";			
			name = ctorTree.getChild(0).getText();
			processFormalParameterList(ctorTree.getChild(1));
			block = new ConstructorBodyNode(this, ctorTree.getChild(2));
		}		
	}
	
	private void processFormalParameterList(Tree parameterTree) {
		parameters = new ArrayList<FormalParameterNode>();
		
		for (int i = 0; i < parameterTree.getChildCount(); i++)
		{
			FormalParameterNode node = new FormalParameterNode(parameterTree.getChild(i), this);
			
			parameters.add(node);
			addSymbol(SymbolFactory.createParameterSymbol(node, CounterManager.getInstance().getNewLocal()));
		}
	}
}
